iT邦幫忙

2022 iThome 鐵人賽

DAY 30
0
Mobile Development

Flutter Developer Learning SwiftUI系列 第 30

Flutter Developer Learning SwiftUI. @State var lesson30 = "總複習"

  • 分享至 

  • xImage
  •  

按照慣例幫各位畫這30天的重點
(趁機靠北一下,為什麼鐵人賽的文章列表10篇就分頁了,至少一次要能看30篇吧,Server loading有這麼重嗎...每次看個文章都要在那邊切來切去)

Source Code 點我

lesson1 = "Hello World"

  • 需要使用Mac + Xcode開發
  • 邊coding就可以邊瀏覽UI Preview(關於Preview更多介紹請點原文)
  • 所有的畫面都是View(Widget☘️☘️☘️),View裡面有個body(build☘️☘️☘️)
  • SwiftUI預設都是對齊中間的⚠️⚠️⚠️
  • 很常使用到Modifier,就是在View後面會有個.xxx的function,例如.padding()
  • 請把專案的最低支援版本改成iOS15
  • 不用再分stateful跟stateless了⚠️⚠️⚠️
  • 對UI的控制力還是滿有限的,常常還是要靠UIKit
  • 首選資源
    • 13:https://www.ethanhuang13.com/p/swiftui-index
    • 肘子:https://www.fatbobman.com/
    • 100 Days of SwiftUI:https://www.hackingwithswift.com/100/swiftui

lesson2 = "啟動畫面"

Yes

  • 如果只是簡單放個圖的話可以用info.plist設定
  • 圖文排版的話就還是要用到Storyboard記得要勾rootViewController
  • 修改沒生效就重啟Simulator= =

lesson3 = "常用Modifier"

  • Modifier就是將View加上效果的function
    例如xxView.padding()
    你就“得到”了一個有padding的view
    一直點下去就可以實現Chaining寫法
  • Modifier會分成所有View都能用的或是特定型別才能用的,絕大部分都是所有View共用
  • 要注意順序
    例如先.background()再.padding()
    還是先.padding()再.background()
    效果就有差了
  • 如果要傳遞不特定型別的View可以透過AnyView
Flutter SwiftUI
Padding .padding
SizedBox .frame
Align .frame
Positioned .offset
(Widget property) .foregroundColor
Container color .background
Container decoration .border

lesson4 = "排列"

Yes

  • VStack HStack ZStack,直的橫的重疊的
  • 要重疊除了用ZStack還可以用.overlay()
  • alignment只能設定交叉方向的⚠️⚠️⚠️
  • 對齊邏輯跟Flutter不同(詳原文)⚠️⚠️⚠️
  • ViewBuilder裡不用寫return,所以一定要用else包起來
  • ViewBuilder有數量的限制(最多10個= =)
  • .background()如果跑出SafeArea,請用edgesIgnoringSafeArea([])
  • AS的option + enter(小燈泡),Xcode可以用command+shift+a☘️☘️☘️
Flutter SwiftUI
Column VStack
Row HStack
Stack ZStack/.overlay()

lesson5 = "顯示文字"

  • 展示各種Text的modifier(詳原文)
  • alignment請用.frame()
  • Text可以相加
Android iOS Flutter SwiftUI
TextView UILabel Text Text

lesson6 = "顯示圖片"

Yes

  • 圖片沒變化可能是忘了加.resizable()
  • 不想要圖片比例跑掉,就是用.scaledToFit 或 .scaledToFill
  • 超出範圍則用.clipped 或 .clipShape
  • init(systemName)就像Flutter的Icon☘️☘️☘️
  • AsyncImage可以讀取網路圖片
Android iOS Flutter SwiftUI
ImageView UIImageView Image Image
Bitmap UIImage ImageProvider -
ScaleType UIViewContentMode BoxFit ContentMode

lesson7 = "按鈕"

Yes

  • 分三種
    • init(_:action:):只有text會有UI回饋
    • init(action:label:):整個View都會有UI回饋
    • .onTapGesture:沒有UI回饋
  • 只要將宣告的變數用@State修飾,就可以在修改state的時候自動完成畫面的更新
  • 要把Button禁用是用.disable(),不像Flutter把function設為null⚠️⚠️⚠️
Android iOS Flutter SwiftUI
Button UIButton RawMaterialButton Button
(尚未研究) UITapGestureRecognizer GestureDetector .onTapGesture

lesson8 = "文字輸入"

Yes

  • init的時候binding一個state(加上$字號),可以用來取得user輸入的內容
  • 輸入完成會執行.onSubmit()
  • style:
    • automatic就是plain
    • plain就是沒有樣式
    • roundedBorder就是跟以前UITextField長得一樣
  • 密碼用SecureField
  • 多行輸入用TextEditor
  • 改鍵盤類型.keyboardType(.numberPad)
  • 有個特別的@FocusState專門來處理Focus
Android iOS Flutter SwiftUI
EditText UITextField TextField TextField
EditText UITextView TextField TextEditor

lesson9 = "挑選器"

Yes

  • 總共有五種default, menu, segmented, wheel, inline
  • default即menu
  • List包住的話會長得不一樣(default跟inline這兩種)
  • 多輪的Picker,要.compositingGroup(),而且要.clipped()
    這兩個Picker才不會重疊
  • ForEach不是流程控制,而是用來重複建立View
Android iOS Flutter(Material) Flutter(Cupertino) SwiftUI
Spinner UIPickerView showBottomSheet CupertinoActionSheet Picker

lesson10 = "日期挑選"

Yes

  • 分成 .compact .wheel .graphical 三種style
  • 若要限制可選範圍,是在init時給in這個參數一個Date range
Android iOS Flutter(Material) Flutter(Cupertino) SwiftUI
DatePickerDialog UIDatePicker showDatePicker/showTimePicker CupertinoDatePicker DatePicker

lesson11 = "對話框"

Yes

  • 從下面彈上來的選單是confirmationDialog(可以點背景取消)
  • Alert改成最多只有兩個按鈕
  • 不能兩個Button同時為.cancel,會閃退
  • 要加TextField要等iOS16= =
Android iOS Flutter SwiftUI
Dialog UIAlertController AlertDialog Alert

lesson12 = "其他控件"

Yes

  • 多餘的label= =請用.labelsHidden()
  • Slider init時可以給step,就可以做到間隔的效果
  • DisclosureGroup搭配List可做開合選項
  • .contextMenu是長按選單(要顯示system image一定要用Label)
  • 比較
    a. Menu
    b. Picker when style menu
    c. .contextMenu

Flutter SwiftUI
Switch Toggle
Slider Slider
DropdownButton Stepper
Positioned .contextMenu
ExpansionTile DisclosureGroup

lesson13 = "顏色"

  • 請把Color想成是一個有延伸性的有顏色的View
  • 推薦參考13的Color 不只是顏色
  • .primary, .secondary通常是拿來給文字使用
  • 系統提供的顏色,在不同模式下顯示的顏色會不一樣
  • iOS的顏色的RGB很奇怪都跟人家不一樣⚠️⚠️⚠️,是用小數點比例來表示
  • Xcode不像AS可以在行號顯示顏色⚠️⚠️⚠️
  • 定義顏色的地方

lesson14 = "形狀"

Yes

  • 常見六種
    • Circle()
    • Ellipse()
    • Capsule()
    • Capsule(style: .continuous)
    • RoundedRectangle(cornerRadius: 20)
    • Rectangle()
  • Shape最常見的用途
    就是用.clipShape()幫View修飾成想要的形狀
    就像Flutter的Container用decoration的shape屬性一樣☘️☘️☘️
  • Shape有些專用modifier,但有些modifier又會把Shape轉成View,很尷尬= =
  • iOS16有出一個AnyShape
  • .background()可加入Gradient
  • .blur()就可顯示模糊效果

lesson15 = "其他View"

Yes

  • Group的主要用途是:拿來幫Group裡的每個View都加上相同的效果
    或是拿來突破ViewBuilder只能十個的限制
  • SwiftUI有提供自訂style的機會
    利用Configuration拿到label跟content
    就可以改成想要的樣式
  • SwiftUI沒有VerticalDivider⚠️⚠️⚠️
  • Spacer遇到Color會變一條窄縫,要達到一樣的效果可以運用Color.clear
  • ProgressView
    • init什麼都沒加就是Loading
    • init給value的就是進度條樣式
Flutter SwiftUI
Divider Divider
VerticalDivider X
Spacer Spacer
ProgressView(菊花) CircularProgressIndicator
ProgressView(進度條) LinearProgressIndicator
Card X

lesson16 = "佈局"

Yes

  • 突破SafeArea的限制用.ignoresSafeArea()
  • Stack裡用.layoutPriority()(Flutter是用Expanded☘️☘️☘️)
    去決定各個View的權重,數字越大地位越高,就可以排擠其他的View
  • SwiftUI不像Flutter有flex⚠️⚠️⚠️,要做到比例效果(例如2:1)可能就要靠GeometryReader
    GeometryReader最常見的功能就像LayoutBuilder一樣☘️☘️☘️,用來取得上層View的大小
  • EmptyView,不管怎樣一定要給一個View的時候可以把它推出去
  • 在info.plist限制app可否橫屏
Flutter SwiftUI
GeometryReader LayoutBuilder
UIScreen MediaQuery
EmptyView Container without child

lesson17 = "圖片挑選"

Yes

  • Image Picker在iOS16以前要自己做
  • SwiftUI專案要使用UIKit:UIViewRepresentable 或 UIViewControllerRepresentable
  • 實現步驟
    1. create struct conform UIViewControllerRepresentable
    2. makeUIViewController
    3. create Coordinator
    4. makeCoordinator
    5. assign context.coordinator to delegate
  • 若要拍照,記得要在info裡添加:向user要求相機授權
  • 再複習一下Image scaled


lesson18 = "滾動"

Yes

  • ScrollView如果是horizontal時,就需要HStack
    不然它就一樣會垂直排列(雖然還是可以水平滾動)
  • 可以同時垂直水平滾動⚠️⚠️⚠️
iOS Flutter SwiftUI
UIScrollView SingleChildScrollView ScrollView

lesson19 = "列表(上)"

Yes

  • 就跟Flutter的ListView一樣,內容可以靜態提供或是動態生成
  • 使用動態的方式必須滿足以下其中之一條件⚠️⚠️⚠️
    1. element必須comform Identifiable
      簡單說就是這個型別必須有個叫id的屬性,而這屬性有唯一性
    2. 必須指定每個element的id來源
      例如String雖然沒有comform Identifiable
      但它可以把他自己本身當作id(就是.self)
  • 有個方便的做法就是直接用Array的indices屬性
    然後就可以拿index去當作id
    什麼都不用改,很方便
  • Swift的enum打switch會自動長出來❤️❤️❤️
  • Swift的enum必須要comform CaseIterable,才能變成陣列(allCases)⚠️⚠️⚠️
  • iOS16之前沒辦法調整分隔線的inset
  • 只要是在NavigationView的List,inset style會失效
Android iOS Flutter SwiftUI
ListView UITableView static cell ListView use children List with content
RecycleView UITableView dynamic cell ListView.builder List with data

lesson20 = "列表(中)"

Yes

  • 側滑/編輯/多選,才會是SwiftUI中使用List的主要理由
    單純畫面的呈現,使用ScrollView會比較有彈性
  • 側滑選項:使用.swipeActions();滑動執行:使用allowsFullSwipe
  • 編輯
    • EditButton用來切換是否為編輯狀態
    • List必須內含ForEach,才能使用關鍵modifier .onDelete() 跟 .onMove()
    • closure裡面分別提供.remove() 跟 .move()
  • 多選只要在List init時提供selection參數,搭配EditButton就能實現功能

lesson21 = "列表(下)"

Yes

  • 下拉刷新使用.refreshable()
  • 搜尋
    • 使用.searchable()
    • 必須搭配NavigationView
      • NavigationView如果是加在這頁的話分隔線會壞掉(inset太多了)
    • suggestions可以提供搜尋建議
  • List裡面只要加了Button,點擊效果就是整個row
  • row裡可以塞個ScrollView+HStack,用來產生多個NavigationLink
    這種不會整個row有點擊效果
  • 打modifier時如果少了前面的這一個點(.padding() 打成padding())
    編譯編得過,但會BAD_ACCESS

lesson22 = "表格"

Yes

  • 二維表格是使用LazyVGrid(垂直) or LazyHGrid(水平)
  • 跟Flutter的GridView概念是類似的(不用自己去算長寬)☘️☘️☘️
  • GridItem這個型別裡的size其實是個enum,有以下三種,預設flexible
    1. fixed: 固定寬度
    2. adaptive: 不小於min的條件下,盡可能多一點(不同裝置可能結果不同)
    3. flexible: 控制排數
  • GridItem.Size也會影響對齊效果,可以看到雖然都是水平置中,但fixed vs flexible的效果並不一樣
  • 交叉方向的alignment在GridView上設定,相同方向的alignment在GridItem上設定(spacing也是一樣的概念)
  • Grid外面要再包一層ScrollView
  • pinnedViews可以決定section要不要有黏貼(sticky)效果
  • 滾動類的View都會無視safearea滾到外面去,加個.padding(.vertical, 0.1)可以解決
  • 如果GridItem沒控好(例如fixed時給的數量太多,或是flexible時minimum太大)
    會有畫面寬度不足被撐開的情況
  • 寫Grid可能會遇到Preview超慢、auto complete失效的情況
Android iOS Flutter SwiftUI
RecycleView(set GridLayoutManager) UICollectionView GridView LazyVGrid & LazyHGrid

lesson23 = "切換頁面"

Yes

  • Link是個按鈕,點了可以開網頁或其他app(like Flutter url_launcher☘️☘️☘️)
  • SwiftUI目前沒有內建的網頁瀏覽器
  • NavigationLink也是一個按鈕,點了就會切換到另一頁(like Flutter Navigator.push()☘️☘️☘️)
    但有個前提是這頁或前一頁必須有用NavigationView包起來
  • .sheet 跟 .fullScreenCover 需要用state去控制是否彈出畫面
  • 關閉使用@Environment取得dismiss
iOS Flutter SwiftUI
openUrl url_launcher Link
UINavigationController Navigator NavigationView
present modal_bottom_sheet .sheet
present modalPresentationStyle = .fullScreen CupertinoPageRoute fullscreenDialog .fullScreenCover

lesson24 = "頁籤(上)"

Yes

  • 基本用法
TabView {
  viewA.tabItme{
    //很適合放Label
  }
  viewB.tabItme{
  
  }
  viewC.tabItme{
  
  }
}
  • iOS上的tabBar規定最多只能五個(不管你螢幕多大)
    超過的部分就會連第五個一起打包成一個叫做'More'的tab
  • 不用特別去控State,會自動處理換頁
  • 要快速返回第一頁(popToRootViewController)
    要特別處理index binding 跟 id,詳見Design+Code
  • 可以用.onChange()去監聽點了tab
Android iOS Flutter SwiftUI
BottomNavigationView UITabBarController BottomNavigationBar TabView

lesson25 = "頁籤(下)"

Yes

  • .tabViewStyle(.page)可以做到滑動換頁效果
  • indexDisplayMode可以顯示page control
  • .animation(.easeInOut, value: selectedPageIndex)
    讓tabView不用手勢也會有切換的動畫
Android iOS Flutter SwiftUI
TabbedActivity UIPageViewController TabController TabView

lesson26 = "生命週期+本地儲存"

Yes

Yes

  • 用Environment能獲取scenePhase三種階段,active inactive background
  • View是用.onAppear() 跟 .onDisappear()
    appear是顯示之前呼叫,disappear是消失之後呼叫
  • 輕量/不敏感的資料要存在app,可以用@AppStorage

App生命週期

iOS Android Flutter SwiftUI
WillEnterForeground onRestart
DidBecomeActive onStart resumed active
WillResignActive onPause inactive
DidEnterBackground onStop paused background

View生命週期

iOS Android Flutter SwiftUI
init onCreate createState init
viewDidLoad initState
viewWillAppear onStart .onAppear()
viewDidLayoutSubviews build body
viewDidAppear onResume
viewWillDisappear onPause
viewDidDisappear onStop .onDisappear()
removeFromSuperview deactivate
deinit onDestroy dispose

本地儲存

iOS Android Flutter SwiftUI
UserDefault SharedPreferences SharedPreferences AppStorage

lesson27 = "call RESTful API"

Yes

  • Flutter用Dio,Swift用Alamofire☘️☘️☘️
  • 專案頁的第三個tab: 'Package Dependencies'可以新增套件
  • 拿到資料後更新state畫面就會自動刷新了
  • Swift的data model必須Conform Codable
    要改屬性的key,則要在CodingKeys裡mapping

lesson28 = "多平台"

  • M1 Mac可以直接run iOS app
  • SwiftUI專案要跑在Mac上,要新增一個Target,然後swift file要勾是跑在哪些Target上
  • 但在Mac平台上有些限制
    例如.background(),就必須明確指出是Color.gray,不能偷懶只寫.gray
    沒辦法無痛轉移,還是要改扣
  • 好像不能用Mac的畫面去preview
  • 有需要做平台判斷可以寫#if os(macOS)
  • Mac如果無法執行可以把Signing Certificate改成Sign to run loaclly
  • 如果Mac app icon無法顯示,就把asset裡的icon檔換個名字
  • 換完icon,simulator要重啟= =
  • 如果加了@State後,跑在Mac上竟然會BAD_ACCESS,clean build一下看看

lesson29 = "環境變數"

Yes

  • @Environment可以取得各種目前環境變數,例如colorScheme可以得知是否使用深色模式
  • 只有特定的View要走深色模式就用.environment(.colorScheme, .dark)
  • 自訂EnvironmentValues
    1. 新建一個struct comform EnvironmentKey
      必須有型別屬性defaultValue
    2. extension EnvironmentValues,新增一個屬性,就是我們之後要存取的環境變數
      裡面的get/set就是用到剛剛新建的EnvironmentKey
  • @Environment由下而上只能讀取,你在這層做的改變,只會從下層開始影響
  • @EnvironmentObject
    1. class必須comform ObservableObject
    2. 裡面的屬性就是我們關心的資料,則要用@Published修飾
    3. 用.environmentObject()這modifier注入
    4. @EnvironmentObject就可以取得object
  • EnvironmentObject不管在哪裡的調整,都會影響所有的地方,跟Flutter的provider或BLoC滿像的☘️☘️☘️
  • 用了@EnvironmentObject之後
    在XXX_Previews裡面也要用.environmentObject()注入一下
    不然無法預覽
  • 推薦連結

對照表大集合

兩家PK

Flutter SwiftUI
Padding .padding
SizedBox .frame
Align .frame
Positioned .offset
(Widget property) .foregroundColor
Container color .background
Container decoration .border
Column VStack
Row HStack
Stack ZStack/.overlay()
Switch Toggle
Slider Slider
DropdownButton Stepper
Positioned .contextMenu
ExpansionTile DisclosureGroup
Divider Divider
VerticalDivider X
Spacer Spacer
ProgressView(菊花) CircularProgressIndicator
ProgressView(進度條) LinearProgressIndicator
Card X
GeometryReader LayoutBuilder
UIScreen MediaQuery
EmptyView Container without child

三缺一

iOS Flutter SwiftUI
UITapGestureRecognizer GestureDetector .onTapGesture
UIScrollView SingleChildScrollView ScrollView
openUrl url_launcher Link
UINavigationController Navigator NavigationView
present modal_bottom_sheet .sheet
present modalPresentationStyle = .fullScreen CupertinoPageRoute fullscreenDialog .fullScreenCover

四家廝殺

Android iOS Flutter SwiftUI
TextView UILabel Text
Button UIButton RawMaterialButton Button
EditText UITextField TextField TextField
EditText UITextView TextField TextEditor
Dialog UIAlertController AlertDialog Alert
ListView UITableView static cell ListView use children List with content
RecycleView UITableView dynamic cell ListView.builder List with data
RecycleView(set GridLayoutManager) UICollectionView GridView LazyVGrid & LazyHGrid
BottomNavigationView UITabBarController BottomNavigationBar TabView
TabbedActivity UIPageViewController TabController TabView.tabViewStyle(.page)
WillEnterForeground onRestart
DidBecomeActive onStart resumed active
WillResignActive onPause inactive
DidEnterBackground onStop paused background
init onCreate createState init
viewDidLoad initState
viewWillAppear onStart .onAppear()
viewDidLayoutSubviews build body
viewDidAppear onResume
viewWillDisappear onPause
viewDidDisappear onStop .onDisappear()
removeFromSuperview deactivate
deinit onDestroy dispose
UserDefault SharedPreferences SharedPreferences AppStorage

四天王有五人

Android iOS Flutter(Material) Flutter(Cupertino) SwiftUI
Spinner UIPickerView showBottomSheet CupertinoActionSheet Picker
DatePickerDialog UIDatePicker showDatePicker/showTimePicker CupertinoDatePicker DatePicker

最後分享兩個SwiftUI做的開源神器

  1. Xcodes
    Xcode的版本真的超多,體積超肥,下載也雷
    這App可以幫你管理並下載多個app
    超方便

  2. cleaner for Xcode
    Xcode不只本身肥,產生出來的資料也很肥
    這App可以快速幫你掃出有哪些資料佔用空間
    隨隨便便就100GB了

    寫SwiftUI更肥,隨便寫個Side project一兩個月就50GB

  3. 偷渡一下我整理的SwiftUI資源集中地
    https://twitter.com/MarkFlyyyyy/status/1558347636302053376

安可?

其實這次系列文下來
還有些主題想試試的
但寫不動了就沒放進去
之後有可能會自行嘗試
雖然已經懶得寫成文章了
但還是問問看
如果你覺得還想看到我的文章的
在標題左邊按讚並留言投票感興趣的主題,該主題超過10票就開寫,私訊連結給你,但時間不定:

  1. floating-panel
  2. 瀑布流
  3. Map
  4. 抽屜側邊欄
  5. FaceID
  6. 一些簡單的animation
  7. UIKit use swiftUI

寫文章真的很累,我想我們不會再見了XD


跨界學習系列文章

Android版:iOS Developer Learning Android. Lesson 30 - 精彩大結局 (重點整理: 看這一天等於看30天)
Flutter版:iOS Developer Learning Flutter. Lesson29 總複習


https://github.com/mark33699/FDLS


上一篇
Flutter Developer Learning SwiftUI. @State var lesson29 = "環境變數"
系列文
Flutter Developer Learning SwiftUI30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言